package com.lw.smartdevice.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.lw.smartdevice.cache.CategoryCache;
import com.lw.smartdevice.cache.SceneCache;
import com.lw.smartdevice.enums.CheckType;
import com.lw.smartdevice.events.ColorCommandEvent;
import com.lw.smartdevice.events.PowerCommandEvent;
import com.lw.smartdevice.exceptions.AddMyBaseException;
import com.lw.smartdevice.exceptions.MyErrorCode;
import com.lw.smartdevice.exceptions.QueryMyBaseException;
import com.lw.smartdevice.mapper.SysDeviceMapper;
import com.lw.smartdevice.pojo.SysCategory;
import com.lw.smartdevice.pojo.SysDevice;
import com.lw.smartdevice.pojo.SysScene;
import com.lw.smartdevice.pojo.SysUserInfo;
import com.lw.smartdevice.service.SysDeviceService;
import com.lw.smartdevice.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author Administrator
 * @version 1.0
 * @description:
 * @date 2022/3/22 16:51
 */
@Service
@Transactional
public class SysDeviceServiceImpl implements SysDeviceService {

    private ApplicationContext context;

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }

    private ObjectMapper objectMapper;

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    private CategoryCache categoryCache;

    @Autowired
    public void setCategoryCache(CategoryCache categoryCache) {
        this.categoryCache = categoryCache;
    }

    private SceneCache sceneCache;

    @Autowired
    public void setSceneCache(SceneCache sceneCache) {
        this.sceneCache = sceneCache;
    }

    private SysDeviceMapper sysDeviceMapper;

    @Autowired
    public void setSysDeviceMapper(SysDeviceMapper sysDeviceMapper) {
        this.sysDeviceMapper = sysDeviceMapper;
    }

    @Override
    public void addDevice(SysDevice device) {
        Assert.isTrue(!device.isEmpty(CheckType.ADD),()->{
            throw new AddMyBaseException("数据不能为空",MyErrorCode.DATA_NULL);
        });
        //判断有没有登录
        SysUserInfo userInfo = SecurityUtils.getLoginUser();
        Assert.notNull(userInfo,()->{
            throw new AddMyBaseException("未登录", MyErrorCode.NOT_LOGIN);
        });
        //检查设备是不是已经被绑定了
        SysDevice sysDevice = sysDeviceMapper.findById(device.getDeviceId());
        Assert.isNull(sysDevice,()->{
            throw new QueryMyBaseException("设备已被绑定",MyErrorCode.DEVICE_ALLREADY_BIND);
        });
        //检查场景是不是空的,是空的就设置为-1,不是空的就检查这个场景属于不属于当前的用户,因为不能添加到别人的场景中
        if(device.getSceneId()==null){
            device.setSceneId(-1L);
        }else if(device.getSceneId()!=-1){
            //当场景不为-1时，判断用户中是否有这个场景
            SysScene sysScene = sceneCache.get(device.getSceneId());
            Assert.isTrue(sysScene!=null&&sysScene.getCreateBy()==userInfo.getUId(),()->{
                throw new QueryMyBaseException("场景不存在",MyErrorCode.SCENE_NOT_EXIST);
            });
        }
        //检查分类是不是存在,分类首先必须存在, 但是可以是状态为0 被删除状态,这是我们的业务要求,我们当前项目是允许添加被删除的分类设备
        SysCategory category = categoryCache.getWithDelete(device.getCategyId());
        Assert.notNull(category,()->{
            throw new QueryMyBaseException("分类不存在",MyErrorCode.CATEGORY_NOT_EXIST);
        });

        device.setBindUserId(userInfo.getUId());//设置绑定用户
        device.setBindTime(new Date());//设置绑定时间
        sysDeviceMapper.addDevice(device);
    }

    @Override
    public int updateById(SysDevice device) {
        //TODO 校验数据
        int result = sysDeviceMapper.updateById(device);
        return result;
    }

    @Override
    public PageInfo<SysDevice> findAll(int page, int limit, SysDevice sysDevice) {
        PageHelper.startPage(page,limit);
        List<SysDevice> deviceList = sysDeviceMapper.findAll();
        //当前设备关联的分类没有获取
        deviceList.forEach(sysDevice1->{
            Long categyId = sysDevice1.getCategyId();//获取到设备关联分类id
            //我们有一个分类缓存，可以获得分类信息
            SysCategory category = categoryCache.getWithDelete(categyId);
            //讲分类信息设置给当前设备
            sysDevice1.setCategory(category);
        });
        PageInfo<SysDevice> pageInfo = new PageInfo<>(deviceList);
        return pageInfo;
    }


    @Override
    public SysDevice findById(String deviceId) {
        return sysDeviceMapper.findById(deviceId);
    }

    @Override
    public void sendControl(String deviceId, String command) {
        //发送命令给指定的设备
        //要先找到要发送的命令是什么
        //命令在设备中，我们传递了一个标识过来
        //我们得先找到分类,分类的id在设备的数据中
        //我们现在知道一个设备的id,通过设备的id查询到分类的id,然后根据分类的id查询到命令,解析命令,然后根据传递的command找到真正的命令
        SysDevice device = sysDeviceMapper.findById(deviceId);
        Assert.notNull(device,()->{
            throw new QueryMyBaseException("设备不存在",MyErrorCode.DEVICE_NOT_EXIST);
        });
        Long categyId = device.getCategyId();//分类id
        SysCategory category = categoryCache.getWithDelete(categyId);//找到分类
        String txCommand = category.getTxCommand();//找到命令
        Assert.notNull(device,()->{
            throw new QueryMyBaseException("操作不存在",MyErrorCode.PERMSSION_NOT_ALLOWED);
        });
        try {
            Map map = objectMapper.readValue(txCommand, Map.class);//解析命令
            Object o = map.get(command);//获取当前操作对应的命令
            if(o instanceof Map){
                int type = (int) ((Map) o).get("type");//命令类型，1是给设备发送，2是给对应的颜色控制器发送
                String realCommand = (String) ((Map<?, ?>) o).get("command");//对应的真实的命令
                switch (type){
                    case 1:
                        //给设备发送map中的command值出去,设备是通过websocket发送命令
                        context.publishEvent(new PowerCommandEvent(deviceId,realCommand));
                        break;
                    case 2:
                        //给颜色控制器发消息
                        context.publishEvent(new ColorCommandEvent(deviceId,command));
                        break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
